D3 single axis jitterplot pan and zoomをscaleTimeに置き換えてみる-@2023-04-08D30
2023-04-11
12:53:43 文字をrect内で折り返し表示するようにした
08:56:31 ここまでだな……
https://gyazo.com/fc07df7cafc80e88c6e7f484ab668707
これ以上は間に合わない
2023-04-08
0から24 * 60の間に収まってくれない
明日調べ直そう
22:37:00 それっぽいのができた!
$ [0,24\times 60]
https://gyazo.com/ccabe2247710e02cb9aadd9605a4c01a
xのdomainを現在日時を含む1週間にする
event dataをタスクリンクから取り出すscript
code:test.js
import { list } from "../takker99%2Ftakker-scheduler/workflow.ts";
const tasks = [];
for await (const { task } of list("takker")) {
if (task.status !== "⬜" && task.status !== "📝") continue;
if (task.startAt?.type !== "date") continue;
if (!task.duration) continue;
if (task.startAt.minutes === undefined) continue;
console.log(task.raw);
tasks.push(task);
}
for await (const { task } of list("takker-memex")) {
if (task.status !== "⬜" && task.status !== "📝") continue;
if (task.startAt?.type !== "date") continue;
if (!task.duration) continue;
if (task.startAt.minutes === undefined) continue;
console.log(task.raw);
tasks.push(task);
}
console.log(tasks);
code:app.js
(async () => {
const {
randomNormal, randomUniform,
scaleLinear, scaleBand,
select, zoom,
interpolateSpectral,
axisLeft, axisTop,
} = d3;
let upload;
{
function i(n){return new Promise((t,l)=>{const e=document.createElement("input");e.type="file",e.accept=n.accept,e.multiple=n.multiple??!1,e.addEventListener("change",()=>{t(n.multiple===!0?e.files?e.files.length===0?void 0:e.files:void 0:e.files?.0??void 0)}),e.addEventListener("error",l),e.click()})} upload=i;
}
const file = await upload({ accept: "application/json" });
const tasks = JSON.parse(await file.text());
console.log(tasks);
// data
// eventの長さ、min単位
const duration = randomNormal(30, 5);
const hour = randomUniform(5, 18);
const start = new Date(2023, 2, 13);
const scheduleMap = new Map();
for (const { name, startAt: { year, month, date, hours, minutes }, duration } of tasks) {
if (year !== 2023) continue;
if (month !== 3) continue;
if (!(16 <= date && date <= 22)) continue;
const key = ${year}-${month + 1}-${date};
const start = hours * 60 + minutes
}
start, events
})).sort((a,b) => new Intl.Collator().compare(a.start, b.start));
console.log(schedule);
// fill
*
* 0, 1に変換した値を、色を決定する関数に渡して色を得る */
const colorScale = scaleLinear()
.domain(extent)
// dimensions
const margin = { top: 20, right: 20, bottom: 30, left: 40 },
svg_dx = 500,
svg_dy = 500;
const chart_dx = svg_dx - margin.right - margin.left;
const chart_dy = svg_dy - margin.top - margin.bottom;
/** y成分をグラフ上の座標に変換する */
const yScale = scaleLinear()
.domain(extent)
const zero = (n) => ${n}.padStart(2, "0");
// y-axis
const yAxis = axisLeft(yScale)
.tickSize(-chart_dx)
// 30分間隔で目盛りを表示
.tickFormat((d) => ${zero(Math.floor(d / 60))}:${zero(d % 60)});
const xScale = scaleBand()
.domain(schedule.map((s) => s.start))
.padding(0.1)
const xAxis = axisTop(xScale);
const { html, render, useState, useCallback, useEffect } = htmPreact;
const App = () => {
const handleMount = useCallback((svg) => {
if (!svg) return;
select(svg).call(
zoom()
.translateExtent([
])
.on("zoom", (e) => {
setYScale(() => e.transform.rescaleY(yScale));
}),
}, []);
useEffect(() => {
// re-scale y axis during zoom; ref 2 select(gX).call(xAxis.scale(xScale));
useEffect(() => {
// re-scale y axis during zoom; ref 2 select(g).call(yAxis.scale(new_yScale));
return html`
<svg width=${svg_dx} height=${svg_dy} ref=${handleMount}>
<g>
${schedule.flatMap(({ start, events }) => events
<rect
x=${xScale(start)}
y=${new_yScale(s)}
width=${xScale.bandwidth()}
height=${new_yScale(s + d) - new_yScale(s)}
fill=${interpolateSpectral(colorScale(s))}
/>
<foreignObject
x=${xScale(start)}
y=${new_yScale(s)}
width=${xScale.bandwidth()}
height=${new_yScale(s + d) - new_yScale(s)}>
<style>
div {
background-color:unset;
font-size: 10px;
line-height: 14px;
word-wrap: break-word;
width:100%;height:100%;
}
</style>
<div>${name}</div>
</foreignObject>`
)
)}
</g>
<g transform="translate(40,0)" ref=${setG} />
<g transform="translate(0,20)" ref=${setGX} />
</svg>
`;
}
render(html<${App} />, document.body);
})().catch((e) => alert(${e.name} ${e.message}\n\n${e.stack}));